iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 21
1
Software Development

Learning Design Pattern in 30 real-case practices系列 第 21

你每天在用,但是可能不知道的... (Iterator 迭代器模式)

  • 分享至 

  • xImage
  •  

Iterator 迭代器模式

需求描述

Amy(PO):

As a 電商老闆
I want 舉辦行銷活動,購物車結帳時:

  1. 書籍雜誌:會員相同類別10本以上八折優惠
  2. 生活用品:會員相同品項$1,000以上九折優惠
    So that 提高網站轉換率及營收

思考設計

JB:
疑!? 我以為這個Backlog已經在昨天: Day20.Visitor 訪問者模式完成了?

Lily:
我們的確是完成了開發,而且Amy測試OK... 所以我們這個iteration的所有backlog都已經完成了! 在明天的iteration review會議之前還有一些時間可以藉由重構來學習Iterator(迭代器模式)這個設計模式。

JB:
Iterator...我好像在哪裡看過...?

Lily:
Iterator(迭代器)提供逐一訪問集合(容器)內部元素的方法。
我們在C#常使用的System.ArraySystem.Collections或是System.Collections.Generic命名空間下,無論是Array,IDictionay,IList...都有實作IEnumerable介面,所以可以透過GetEnumerator()這個方法來取得迭代器,讓我們可以搭配foreach來訪問這些集合(容器)的元素。

JB:
那我們要如何利用Iterator(迭代器模式)來重構這個需求呢?

Lily:
原本的主程式是從購物車篩選特定種類的商品(例如...書籍),再放到訪問者模式的ObjectStructure:

IObjectStructure checkout = new ObjectStructure();
//Attach the elements into ObjectStructure
this.Shopcart.Where(item=>item.ProductType.Equals(ProductTypeEnum.Book)).ToList().ForEach(item => {
    checkout.Attach(item);
});

我們要自訂可以過濾商品種類的迭代器,將特定種類的商品放入ObjectStructure:

IObjectStructure checkout = new ObjectStructure();
var iterator = new ConcreteIterator(ProductTypeEnum.Book);
while (!iterator.IsFinal)
{
    var elm = this._iterator.Next();
    checkout.Add(elm);
}

另外,在迭代器模式中定義了封裝了Iterator的介面:Aggregate
所以我們最後的程式碼如下:

IObjectStructure checkout = new ObjectStructure();
Aggregate aggregate = new ConcreteAggregate(ProductTypeEnum.Book);
checkout.Elements = aggregate.GetAll();

JB:
看來我們已經有單元測試程式碼了,
讓我們開始Red-Green-Refactor吧!

定義

迭代器提供一種方法來逐一訪問對象的元素,而不暴露其結構。 (https://en.wikipedia.org/wiki/Iterator_pattern)

UML

  1. Iterator : 提供存放集合,以及操作、尋覽集合的方法
  2. Aggregate:將Iterator封裝給Client使用的介面
  3. ConcreteAggregate 依賴於 ConcreteIterator,ConcreteIterator則關聯ConcreteIterator

Iterator

注意我們刻意在Iterator的建構子帶入參數:ProductTypeEnum prodType
目的在於尋覽內部元素時,跳過不屬於該商品種類的元素。

  • C#
public abstract class Iterator
{
    public abstract IElement Current();
    public abstract IElement First();
    public abstract IElement Next();
    public abstract bool IsFinal {get;}
    public abstract void Add(IElement elm); 
}

public class ConcreteIterator : Iterator
{
    private Aggregate _aggregate = null;
    private ProductTypeEnum _prodType;
    private int _pointer = 0;
    private List<IElement> _collection = new List<IElement>();

    public override bool IsFinal
    {
        get
        {
            if (this._pointer >= (this._collection.Count - 1))
                return true;
            else
                return false;
        }
    }

    public ConcreteIterator(Aggregate aggregate, ProductTypeEnum prodType)
    {
        this._aggregate = aggregate;
        this._prodType = prodType;
    }

    public override IElement Current()
    {
        if (this._pointer >= this._collection.Count)
        {
            throw new IndexOutOfRangeException();
        }
        else
        {
            var elm = this._collection[this._pointer];
            while (!elm.ProductType.Equals(this._prodType))
            {
                this._pointer++;
                if (this._pointer >= this._collection.Count)
                    return null;
                else
                    elm = this._collection[this._pointer];
            }

            return this._collection[this._pointer];
        }
    }

    public override IElement First()
    {
        this._pointer = 0;
        return this.Current();
    }

    public override IElement Next()
    {
        this._pointer++;
        return this.Current();
    }

    public override void Add(IElement elm)
    {
        this._collection.Add(elm);
    }
}
  • Python
from abc import ABC, abstractmethod
from Elements import Element, ProductTypeEnum
import Aggregate

class Iterator(ABC):
    @abstractmethod
    def current(self) -> Element:
        pass

    @abstractmethod
    def first(self) -> Element:
        pass
    
    @abstractmethod    
    def next(self) -> Element:
        pass

    @abstractmethod
    def isFinal(self) -> bool:
        pass

    @abstractmethod
    def add(self, elm:Element):
        pass



class ConcreteIterator(Iterator):

    def __init__(self, aggregate: Aggregate, prodType: ProductTypeEnum):
        self.aggregate = aggregate
        self.prodType = prodType
        self.pointer = 0
        self.collection = []

    def current(self) -> Element:
        if (self.pointer >= len(self.collection)):
                raise Exception("IndexOutOfRangeException:pointer")
        else:
            elm = self.collection[self.pointer]
            while (not elm.productType==self.prodType):
                self.pointer = self.pointer + 1
                if (self.pointer >= len(self.collection)):
                    return None
                else:
                    elm = self.collection[self.pointer]

            return self.collection[self.pointer]

    def first(self) -> Element:
        self.pointer = 0
        return self.current()
    
    def next(self) -> Element:
        self.pointer = self.pointer +1
        return self.current()
        
    def isFinal(self) -> bool:
        if (self.pointer >= (len(self.collection) - 1)):
            return True
        else:
            return False

    def add(self, elm:Element):
        self.collection.append(elm)

Aggregate

  • C#
public abstract class Aggregate
{
    public abstract Iterator GetIterator();
    public abstract List<IElement> GetAll();
    public abstract void Add(IElement elm);
}

public class ConcreteAggregate : Aggregate
{
    private Iterator _iterator;

    public ConcreteAggregate(ProductTypeEnum prodType)
    {
        this._iterator = new ConcreteIterator(this, prodType);
    }
    public override void Add(IElement elm)
    {
        this._iterator.Add(elm);
    }

    public override Iterator GetIterator()
    {
        return this._iterator;
    }

    public override List<IElement> GetAll()
    {
        List<IElement> list = new List<IElement>();
        list.Add(this._iterator.First());

        while (!this._iterator.IsFinal)
        {
            var elm = this._iterator.Next();
            if (elm != null)
                list.Add(elm);
        }
        return list;
    }
}
  • Python
import Iterator
from abc import ABC, abstractmethod
from Elements import Element, ProductTypeEnum


class Aggregate(ABC):

    @abstractmethod
    def getIterator(self) -> Iterator.Iterator:
        pass

    @abstractmethod    
    def getAll(self):
        pass

    @abstractmethod
    def add(self, elm:Element):
        pass
        

class ConcreteAggregate(Aggregate):
    
    def __init__(self, prodType:ProductTypeEnum):
        self.iterator = Iterator.ConcreteIterator(self, prodType)

    def add(self,elm:Element):
        self.iterator.add(elm)

    def getIterator(self) -> Iterator.Iterator:
        return self.iterator

    def getAll(self):
        list = []
        list.append(self.iterator.first())

        while (not self.iterator.isFinal()):
            elm = self.iterator.next()
            if (elm != None):
                list.append(elm)

        return list

--

透過以上實作的迭代器,我們可以將Day20.Visitor 訪問者模式的主程式改寫如下(執行結果不變):

  • C#
private List<IElement> Shopcart = null;
this.Shopcart = new List<IElement>(){
    //Some products..
};

IObjectStructure checkout = new ObjectStructure();
checkout.Elements = aggregate.GetAll();

//Accept all the elements and execute the strategy from certain Visitor 
checkout.Accept(new VisitorDiscount4Count());
  • Python
_shopcart = [
    #Some products..
]

aggregate = ConcreteAggregate(Elements.ProductTypeEnum.Book)
for prod in _shopcart:
    aggregate.add(prod)

checkout = ObjectStructure()
# Attach the elements into ObjectStructure
for item in aggregate.getAll():
    checkout.attach(item)

# Accept all the elements and execute the strategy from certain Visitor
checkout.accept(VisitorDiscount4Count())

Sample Codes

  1. C#
  1. Python

Reference


上一篇
優惠方案每天變! 購物車怎麼結帳才不會算到手抽筋? (Visitor 訪問者模式)
下一篇
讓你當一天艾森豪,來指揮諾曼地登陸作戰! (Command 命令模式)
系列文
Learning Design Pattern in 30 real-case practices30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言